# Title: SchedulerCLI.py
# Author: Arnold Baghomian
# Project: Facultimes
# This is an Oregon State University built as a requirement for CS319. The
# project is a collaboration between Matthew Tanner, Arnold Baghomian, and
# Joseph Helgeson.

import npyscreen
import curses
import datetime
import Scheduler
import time
import logging

# this is the class that manages the forms - each form is one screen
class ScheduleApp(npyscreen.NPSAppManaged):
    # create the 3 forms and manage them
    def onStart(self):
        # effectively disable logging by setting level to CRITICAL
        #logging.basicConfig(filename="scheduler.log",level=logging.CRITICAL)
        #logging.info("starting log")

        npyscreen.setTheme(npyscreen.Themes.ElegantTheme)

        self.mf = self.addForm("MAIN", MainForm)

        self.sf = self.addForm("NAMES", SelectNamesForm)

        self.rf = self.addForm("RESULTS", ResultForm)


    # switch between the 3 forms by their name
    def changeForm(self, name):
        #logging.info("changeForm")
        self.switchForm(name)
        # don't need the history
        self.resetHistory()

    # display help screen
    def helpWindow(self, *args, **keywords):
        # put up message until user hits OK
        # since any user of this will be a new user give them instructions
        # for a better first experience
        npyscreen.notify_confirm("<tap enter twice to continue> <use arrow keys to read more>\n~\n"+
                                 "Pick a meeting date-time window and a set of faculty names "+
        "from the main screen. " +
	"\n~\n[*Some navigation keys work depending on your terminal capabilities.*]\n~\n"+
        "Select an input element with mouse/tab/shift-tab/arrow keys; "+
        "activate it with space-bar/enter-key. "+
        "Navigate long lists with PgDwn / PgUp / Home / End / Arrow keys etc.\n~\nDate: enter key selects date;"+
        " dwmyDWMY keys move through"+
        " dates (see legend). t = today, c = clear.\n~\n"+ "Name list: Enter/Space to select. "+
        "l = search for matching names, L = clear search, n = next match, p = previous match.\n~\n"
        "When inputs are done the Results (calculate) button (^R) will take you"+
                                 " to the results screen displaying both what times "+
        "everyone can attend and who can attend the whole time. Exit button (^E) ends program. "+
        "Main (back) button (^M) goes back to main screen. "+ "Hot keys for buttons are "+
        "named at the top of the screens. "+"\n~\nIf terminal does color: Yellow = buttons, Green = labels,"+
        " White = data.\n~\n<tap enter twice to continue>",
        title = "--------------- Welcome to Facultimes Meeting Scheduler Tool -------------------",
        form_color = 'CAUTION', wrap = True, wide = True)


# buttons to do various tasks
# this button applies the selected faculty names to the selected names list
# and returns to the MAIN form. It uses a global boolean to signal the names list should change
class ApplyButton(npyscreen.ButtonPress):
    def whenPressed(self):
        global canceled
        canceled = False
        #logging.warning("ApplyButton")
        self.parent.parentApp.changeForm("MAIN")
        #self.parent.editing = False

# this button returns to the MAIN form without changing the selected names list
# It uses a global boolean to signal the names list should NOT change
class CancelButton(npyscreen.ButtonPress):
    def whenPressed(self):
        global canceled
        #logging.warning("ApplyButton")
        canceled = True
        self.parent.parentApp.changeForm("MAIN")
        #self.parent.editing = False

# this button displays a help window from the main screen
class HelpButton(npyscreen.ButtonPress):
    def whenPressed(self):
        self.parent.parentApp.helpWindow()

# this button moves back to the MAIN form from the RESULTS form
# if the user wants to change some of the input parameters
class BackButton(npyscreen.ButtonPress):
    def whenPressed(self):
        #logging.info("BackButton")
        self.parent.change_forms("MAIN")


# this button updates input parameters - the start/end times and the selected faculty name list -
# from the user interface objects. It is used in the MAIN form
class UpdateButton(npyscreen.ButtonPress):
    def whenPressed(self):
        #logging.info("UpdateButton")
        self.parent.updateForm()
        self.parent.change_forms("RESULTS")

# this button exits the program. It is used in both the MAIN and RESULTS forms
class ExitButton(npyscreen.ButtonPress):
    def whenPressed(self):
        global exiting
        #logging.info("ExitButton")
        #have to flag SelectNames not to return to MAIN form
        exiting = True
        self.parent.parentApp.setNextForm(None)
        self.parent.editing = False

# this button initiates moving to the NAMES form for picking names from the list
class PickNamesButton(npyscreen.ButtonPress):
    def whenPressed(self):
        #logging.info("PickNamesButton")
        self.parent.parentApp.changeForm("NAMES")


# this interface class lets the user pick minutes a list
class MinutesComboBox(npyscreen.ComboBox):
    def display_value(self, vl):

        if vl == '':
            return '00'
        else:
            svl = "%02d" % vl
            return svl

# this interface class lets the user pick hours from a list
class HoursComboBox(npyscreen.ComboBox):
    def display_value(self, vl):

        if vl == '':
            return '00:'
        else:
            svl = "%02d:" % vl
            return svl

# this form is for picking out faculty names from a list
class SelectNamesForm(npyscreen.FormBaseNew):
    # set form parameters and create the user interface objects
    def create(self):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList, canceled, exiting
        exiting = False # flag for user wanting to exit instead of return to main
        self.cycle_widgets  = True
        canceled = False
        #logging.info("SelectNamesForm")
        selectedFacultyList = []

        #test list was input like this
        #f = open("instructors.txt")
        #facList = f.readlines()
        #f.close()

        self.labelHelp = self.add(npyscreen.TitleFixedText,
        name = \
          "PICK FACULTY NAMES: select buttons with mouse, tab / shift-tab, or hot keys.",
          rely = 0,  editable= False)
        self.labelFacultyList = self.add(npyscreen.TitleFixedText, name = 'Faculty Selected', rely = 4,
            editable= False)
        self.add(npyscreen.TitleText, name="keys: lLnp PgUp PgDn Home End space enter arrows", relx = 3, rely = 1,
                 editable = False)
        self.add(npyscreen.TitleText, name="Button Bar:", relx = 3, rely = 2,
                 editable = False)

        self.facultyList = self.add(npyscreen.MultiSelect, columns = facultyListWidth,
        values = facList)
        self.applyButton = self.add(ApplyButton, name="Apply ^A", relx = 15, rely = 2)
        self.cancelButton = self.add(CancelButton, name="Cancel ^L", relx = 29, rely = 2)
        self.exitButton=self.add(ExitButton, relx = 43, rely = 2,  name = 'Exit (^E)')
        self.helpButton=self.add(HelpButton, relx = 57, rely =  2, name = 'Help (^H)')
        self.add_handlers({"^L": self.cancelForm})
        self.add_handlers({"^A": self.applyForm})
        self.add_handlers({"^H": self.parentApp.helpWindow})
        self.add_handlers({"^E": self.exitForm})


    # used to perform a button function with a hotkey
    def cancelForm(self, *args, **keywords):
        self.cancelButton.whenPressed()

    # used to perform a button function with a hotkey
    def applyForm(self, *args, **keywords):
        self.applyButton.whenPressed()

    # this updates the gobal selected names list from the user interface object if APPLY was picked
    # and then it tells the form manager to go to the MAIN form
    def afterEditing(self):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList, canceled, exiting

        if not canceled:
            selectedFacultyList = self.facultyList.get_selected_objects()
        if not exiting:
            self.parentApp.changeForm("MAIN")

    # this returns a list of the selected
    # not currently used
    def getList(self):
        return self.facultyList.get_selected_objects()

    # handler for hotkey to exit form
    def exitForm(self, *args, **keywords):
        self.exitButton.whenPressed()

# this form is for getting user input of times and displaying selected names
class MainForm(npyscreen.FormBaseNew):
    # set form parameters and create the user interface objects
    def create(self):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList

        self.cycle_widgets  = True
        self.scheduler = Scheduler.Scheduler()
        #logging.warning("MainForm")

        # initialize the output list of names
        listAvailable = []
        LISTMINUTES = range(60)
        LISTHOURS = range(24)

        selectedFacultyList = None

        facList = self.scheduler.validUsers
        if facList == -1:
            error = self.scheduler.error
            npyscreen.notify_confirm(error,title="Facultimes Error",form_color='CRITICAL',wrap=True,wide=True)
            facList = [error]
        #test input data:
        #f = open("instructors.txt")
        #facList = f.readlines()
        #f.close()

        facultyListWidth = 35
        facultyAvailableWidth = 35
        timeListWidth = 30

        self.columns = 132

        # title   help
        self.labelHelp = self.add(npyscreen.TitleFixedText, name = "select buttons with mouse or tab, enter/space to push, ^R results, ^E exits",
            rely = 0, editable= False)

        # input time area

        timeWindowYOffset = 3
        self.labelTimeWindows = self.add(npyscreen.TitleFixedText, name = 'Select Meeting Time Span',
            rely = timeWindowYOffset, editable= False)

        # get/show start time

        changeXEnd = 3+30+3+2+2 - 13
        hoursOffset = 18+9
        self.labelStartTime = self.add(npyscreen.TitleFixedText, name = "start",   editable = False,
                                       rely = timeWindowYOffset+1)
        self.meetingStartDate = self.add(npyscreen.DateCombo,allowPastDate = False, width  = 18, useDateTime = True,
                                         rely = timeWindowYOffset+1, relx = 9, columns = 18)
        current_time = datetime.datetime.now().time()

        # use today as default start and end dates unless it is after 4pm
        # in which case use tomorrow
        if current_time.hour < 16:
            self.meetingStartDate.value = self.meetingStartDate.date_or_datetime().today()
        else:
            self.meetingStartDate.value = self.meetingStartDate.date_or_datetime().today() + datetime.timedelta(1)
        self.hoursStart = self.add(HoursComboBox, relx = hoursOffset+1, rely = timeWindowYOffset+1, width = 3,
                                   values = LISTHOURS, value = 10)
        self.hoursStart.value = 5
        self.minutesStart = self.add(MinutesComboBox, relx = hoursOffset+4, rely = timeWindowYOffset+1, width = 2,
                                     values = LISTMINUTES)
        self.minutesStart.value = 0

        # get/show end time

        self.labelEndTime = self.add(npyscreen.TitleFixedText, name="end  ",   editable = False,
                                     rely = timeWindowYOffset+2)
        self.meetingEndDate = self.add(npyscreen.DateCombo, allowPastDate = False, width = 18,
                                       rely = timeWindowYOffset+2, columns = 18, relx = 9)
        self.meetingEndDate.value = self.meetingStartDate.value
        self.hoursEnd = self.add(HoursComboBox, rely = timeWindowYOffset+2, relx = hoursOffset+1, width = 3,
                                 values = LISTHOURS)
        self.hoursEnd.value = 20
        self.minutesEnd = self.add(MinutesComboBox, rely = timeWindowYOffset+2, relx = hoursOffset+4, width = 2,
                                   values = LISTMINUTES)
        self.minutesEnd.value = 0
        timeAvailableYOffset = 20

        # Select Faculty Names

        self.pickNamesButton=self.add(PickNamesButton,  rely = 7,  name = 'Select Faculty Names Button')
        self.labelFacultyList = self.add(npyscreen.TitleFixedText, name = 'SELECTED FACULTY', rely = 9, editable= False)
        self.facultyListSelected = self.add(npyscreen.MultiSelect, columns = facultyListWidth, rely = 10,
        values=[],scroll_exit=True,exit_left=True,exit_right=True)

        self.updateButton=self.add(UpdateButton, relx = 0, rely = 1, name = 'Results (^R)')
        self.exitButton=self.add(ExitButton, relx = 23, rely = 1,  name = 'Exit (^E)')
        self.helpButton=self.add(HelpButton, relx = 46, rely =  1, name = 'Help (^H)')

        self.add_handlers({"^E": self.exitForm})
        self.add_handlers({"^R": self.updateForm})
        self.add_handlers({"^H": self.parentApp.helpWindow})

        # display help screen
        self.parentApp.helpWindow()


    # this sets the interface object's display of selected names to the global list
    def beforeEditing(self):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList
        self.facultyListSelected.values = selectedFacultyList

    # used to press a button by hotkey
    def exitForm(self, *args, **keywords):
        self.exitButton.whenPressed()

    # not used currently as there is no back button on this form
    def backForm(self, *args, **keywords):
        self.backButton.whenPressed()

    # this updates the input parameter values from the form's user interface objects
    def updateForm(self, *args, **keywords):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList, MAX_TIMES
        npyscreen.notify("Please wait while I find times and people for your meeting...",
                         title = "Facultimes - Meeting Scheduler Tool", form_color = 'CAUTION' ,
                         wrap = True, wide = True)
        # make sure that at least one name was selected
        if len(selectedFacultyList) == 0:
            return
        self.facultyListSelected.values = selectedFacultyList
        self.scheduler.users = selectedFacultyList
        sd = self.meetingStartDate.value
        # make sure at least a start time is set
        if sd == None:
            return
        sdt = datetime.datetime(sd.year,sd.month,sd.day,self.hoursStart.value, self.minutesStart.value)
        ed = self.meetingEndDate.value
        edt = datetime.datetime(ed.year,ed.month,ed.day,self.hoursEnd.value, self.minutesEnd.value)
        # make sure end time is past start time
        if edt - sdt <= datetime.timedelta(0):
            return
        # send selected input parameters to backend scheduler
        self.scheduler.startWindow = sdt
        self.scheduler.endWindow = edt
        if(self.scheduler.schedule()) == -1:
            error = self.scheduler.error
            npyscreen.notify_confirm(error,title="Facultimes Error",form_color='CRITICAL',wrap=True,wide=True)
            return
        # get the result list of names
        attendees = self.scheduler.attendees
        # set the display element list of result names
        self.parentApp.rf.facultyListAvailable.values = attendees
        # get the result list of time intervals      
        gridDateTimes = self.scheduler.times
        gridTimes = []
        for w in gridDateTimes:
            # convert dates in list to strings from date-time objects
            strSd = w[0].strftime('%x')
            strEd = w[1].strftime('%x')
            strSt = w[0].strftime('%H:%M')
            strEt = w[1].strftime('%H:%M')
            if strSd == strEd:
                gridTimes.append(strSt + ' ' + ' - ' + strEt + ', ' + strEd)
            else:
                gridTimes.append(strSt + ', ' + strSd + ' - ' + strEt + ', ' + strEd)
        # put results into display element for time intervals
        self.parentApp.rf.gridAvailable.values = gridTimes
        # switch to RESULTS form from MAIN form
        self.parentApp.changeForm("RESULTS")

    # this is used to tell the form manager to display another form by name
    def change_forms(self, name):
            self.parentApp.changeForm(name)

# this form displays the results of scheduling as a list of times and another list of names
class ResultForm(npyscreen.FormBaseNew):
    # set form parameters and create the user interface objects
    def create(self):
        global facList,listAvailable, facultyAvailableWidth,facultyListWidth,timeListWidth, scheduler, \
            selectedFacultyList, MAX_TIMES

        # max number of meeting times proposed (due to screen size limitations)
        MAX_TIMES = 7
        #logging.warning("ResultForm")
        self.cycle_widgets  = True
        LISTMINUTES = range(60)
        LISTHOURS = range(24)


        # title   help
        self.labelHelp = self.add(npyscreen.TitleFixedText, name = "select buttons with mouse or tab, enter/space to push, ^M go back. ^E exit",
                                  rely = 0, editable= False)

        self.backButton=self.add(BackButton, relx = 0, rely = 1, name = 'Main (^M)')
        self.exitButton=self.add(ExitButton, relx = 20, rely = 1,  name = 'Exit (^E)')
        self.helpButton=self.add(HelpButton, relx = 46, rely =  1, name = 'Help (^H)')

        # times that work for all the selected names

        timeAvailableYOffsetp1 = 3
        self.labelWindowsAvailable = self.add(npyscreen.TitleFixedText,
                                              name = 'Times that all selected faculty are free:',
                                              editable= False, rely = timeAvailableYOffsetp1)
        self.gridAvailable = self.add(npyscreen.Pager, rely = timeAvailableYOffsetp1 + 1, max_height= MAX_TIMES,
                                      editable = True)
        # initialise the result of list times
        self.gridAvailable.values  = []
        # values used for testing purposes:
        #self.gridAvailable.values.append(['14 August, 2014 12:00    to','15 August, 2014 13:00'])
        #self.gridAvailable.values.append(['15 September, 2014 16:00 to','15 August, 2014 17:00'])


        # output lise of the faculty that are free for all requested times

        self.labelFacultyAvailable = self.add(npyscreen.TitleFixedText,
                                              name = 'Selected faculty that can attend all times:',
                                              rely = MAX_TIMES + 4, editable= False)

        self.facultyListAvailable = self.add(npyscreen.MultiLine, width = facultyAvailableWidth,
                                             rely = MAX_TIMES + 5, editable=True, values = listAvailable )

        # attach handler methods to hotkeys
        self.add_handlers({"^H": self.parentApp.helpWindow})
        self.add_handlers({"^E": self.exitForm})
        self.add_handlers({"^M": self.backForm})

    # set the MAIN form's name list user interface object to the global selected names list
    def beforeEditing(self):
        self.parentApp.mf.facultyListSelected.values = selectedFacultyList

    # handler for hotkey to exit form
    def exitForm(self, *args, **keywords):
        self.exitButton.whenPressed()

    # handler for hotkey to return to MAIN form
    def backForm(self, *args, **keywords):
        self.backButton.whenPressed()

    # this tells the form managing object display the MAIN form
    def change_forms(self, *args, **keywords):
            self.parentApp.changeForm("MAIN")

# this is the entry point
if __name__ == '__main__':
    TA = ScheduleApp()
    TA.run()
